Predição de preços de Meio de campo em R


# Setup Célula oculta para o setup:

Obtenção de Dados

Carrega o nosso CSV O CSV foi gerado através de inner_joins das consultas do banco de dados cedido pelo do professor … As tabelas utilizadas foram: ‘futebol.players’, ‘futebol.habilities’, ‘futebol.features’ e ‘futebol.financial’ e o resultado final é lido logo abaixo. Observação: É necessário alterar o path do read_csv para apontar corretamente o arquivo .csv de acordo com o seu S. O.

df <- read_csv("/media/njaneto/HD1/FIAP/PROGRAMANDO_IA_COM_R/fifa18-data-analysis/model/data/fifa18.csv", locale = locale(encoding = "ISO-8859-1"))
Registered S3 method overwritten by 'cli':
  method     from
  print.tree tree
Rows: 17994 Columns: 57
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (9): name, full_name, club, league, nationality, Position, work_rate_att, work_rate_def, preferred_foot
dbl (48): ID, special, eur_value, eur_wage, eur_release_clause, crossing, finishing, heading_accuracy, short_passin...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
setDT(df)

Análise dos dados

É muito importante prepararmos os dados para a análise e predição que iremos fazer.

Visualização inicial dos dados

A primeira coisa que iremos fazer é obter o número de linhas e colunas do dataframe:

dim(df)
[1] 17994    57

Depois trazemos os primeiros registros utilizando a funão head()

head(df)

E por fim incluímos todos os continentes no dataframe

Africa<-c('Algeria','Angola','Benin','Botswana','Burkina','Burundi','Cameroon','Cape Verde','Central African Republic','Chad','Comoros','Congo','Congo Democratic Republic of','Djibouti','Egypt','Equatorial Guinea','Eritrea','Ethiopia','Gabon','Gambia','Ghana','Guinea','Guinea-Bissau','Ivory Coast','Kenya','Lesotho','Liberia','Libya','Madagascar','Malawi','Mali','Mauritania','Mauritius','Morocco','Mozambique','Namibia','Niger','Nigeria','Rwanda','Sao Tome and Principe','Senegal','Seychelles','Sierra Leone','Somalia','South Africa','South Sudan','Sudan','Swaziland','Tanzania','Togo','Tunisia','Uganda','Zambia','Zimbabwe','Burkina Faso')
Antarctica<-c('Fiji','Kiribati','Marshall Islands','Micronesia','Nauru','New Zealand','Palau','Papua New Guinea','Samoa','Solomon Islands','Tonga','Tuvalu','Vanuatu')
Asia<-c('Afghanistan','Bahrain','Bangladesh','Bhutan','Brunei','Burma (Myanmar)','Cambodia','China','East Timor','India','Indonesia','Iran','Iraq','Israel','Japan','Jordan','Kazakhstan','North Korea','South Korea','Kuwait','Kyrgyzstan','Laos','Lebanon','Malaysia','Maldives','Mongolia','Nepal','Oman','Pakistan','Philippines','Qatar','Russian Federation','Saudi Arabia','Singapore','Sri Lanka','Syria','Tajikistan','Thailand','Turkey','Turkmenistan','United Arab Emirates','Uzbekistan','Vietnam','Yemen','Russia')
Europe<-c('Albania','Andorra','Armenia','Austria','Azerbaijan','Belarus','Belgium','Bosnia and Herzegovina','Bulgaria','Croatia','Cyprus','Czech Republic','Denmark','Estonia','Finland','France','Georgia','Germany','Greece','Hungary','Iceland','Ireland','Italy','Latvia','Liechtenstein','Lithuania','Luxembourg','Macedonia','Malta','Moldova','Monaco','Montenegro','Netherlands','Norway','Poland','Portugal','Romania','San Marino','Scotland','Serbi','Slovakia','Slovenia','Spain','Sweden','Switzerland','Ukraine','England','Vatican City','Republic of Ireland','Wales')
North_america<-c('Antigua and Barbuda','Bahamas','Barbados','Belize','Canada','Costa Rica','Cuba','Dominica','Dominican Republic','El Salvador','Grenada','Guatemala','Haiti','Honduras','Jamaica','Mexico','Nicaragua','Panama','Saint Kitts and Nevis','Saint Lucia','Saint Vincent and the Grenadines','Trinidad and Tobago','United States')
South_america<-c('Argentina','Bolivia','Brazil','Chile','Colombia','Ecuador','Guyana','Paraguay','Peru','Suriname','Uruguay','Venezuela')

df[, continent:= df$nationality]
df <- df %>% relocate(continent, .after = nationality)

df$continent[df$continent %in% Africa ] <- "Africa"
df$continent[df$continent %in% Antarctica ] <- "Antarctica"
df$continent[df$continent %in% Asia ] <- "Asia"
df$continent[df$continent %in% Europe ] <- "Europe"
df$continent[df$continent %in% North_america ] <- "North_america"
df$continent[df$continent %in% South_america ] <- "South_america"

Graficos de analise dos dados

Antes de iniciar a nossa análise, iremos plotar o dataframe para analisar o gráfico resultante, para identificar as melhores variáveis.

plot_intro(df)

Usamos a função plot_missing() para mostrar quais colunas possuem dados faltantes em nosso dataframe.

plot_missing(df)

Então criamos um mapa mostrando a distribuição dos jogadores por país.

pais <- df[,.N, by=nationality]
fr <- joinCountryData2Map(dF=pais, joinCode = "NAME", nameJoinColumn = "nationality", verbose = F)
148 codes from your data successfully matched countries in the map
16 codes from your data failed to match with a country code in the map
95 codes from the map weren't represented in your data
mapCountryData(mapToPlot = fr,nameColumnToPlot = "N",catMethod = "fixedWidth",
               oceanCol = "steelblue1",missingCountryCol = "white",
               mapTitle = "Jogadores por país",
               aspect = "variable") 
Warning in plot.window(xlim = xlim, ylim = ylim, asp = aspect) :
  NAs introduzidos por coerção

Histograma de jogadores por overall Conseguimos identificar uma grande concentração de jogadores com overall entre 60 e 73

histogram <- plot_ly(x = ~df$overall,
                     type = "histogram",
                     marker = list(color = "lightgray",
                                   line = list(color = "darkgray",
                                               width = 1))) %>%
  layout(title = "Histograma por overall dos jogadores (Center-mid)",
         xaxis = list(title = "Overall",
                      zeroline = FALSE),
         yaxis = list(title = "Quantidade",
                      zeroline = FALSE))

histogram

Filtrando Meio campo

Ainda na análise, filtramos os meios campo dos times

MID_EUROPE <- df %>%
  filter(Position == "Center-mid" & continent == "Europe")

head(MID_EUROPE)

Então, selecionamos os meios campos da variável

#-- base para validacao
MID_NOT_EUROPE <- df %>%
  filter(Position == "Center-mid" & continent != "Europe")

head(MID_NOT_EUROPE)

Removendo dados

Pro último, limpamos o dataframe removendo missing data e variáveis não númericas da base de treino.

MID_EUROPE <- MID_EUROPE %>% 
  select_if(~ !any(is.na(.))) %>%
  select_if(~ any(is.numeric(.)))

boxplot(MID_EUROPE)

E por fim, removemos os missing datas e variáveis não numéricas da nossa base de validação.

fifa.18.cm <- MID_NOT_EUROPE %>% 
  select_if(~ !any(is.na(.))) %>%
  select_if(~ any(is.numeric(.)))

head(fifa.18.cm)

Treinamento

Nessa etapa iniciamos o processo de treinamento do nosso modelo.

Boxplot

Grafico de boxplot nos mostra que existem jogadores com valores discrepantes em relação as demais.

boxplot(MID_EUROPE)

NA
NA

Correlação

Gráfico para mostrar a correlação de todas as variáveis.

corrMatrix <- cor(MID_EUROPE)
corrplot.mixed(corrMatrix, 
               lower = "ellipse", 
               upper = "number",
               tl.pos = "lt",
               tl.col = "black",
               order="hclust",
               hclust.method = "ward.D",
               addrect = 3)

Definição do modelo de ML

Testamos as funções ml() e randomFlorest() e a função randomFlorest() se mostrou mais assertivo para o nosso modelo.

set.seed(1)
reg.test <- randomForest(formula = eur_value ~ ., 
                         data = MID_EUROPE, 
                         ntree=100, 
                         proximity=TRUE, 
                         localImp=TRUE)
plot(reg.test)

Predição

Avaliação

Predição dos preços

predito = predict(reg.test, MID_EUROPE)
print(paste("R2: ", R2_Score(predito, MID_EUROPE$eur_value) ) )
[1] "R2:  0.991973709944234"
print(paste("MSE: ", MSE(predito, MID_EUROPE$eur_value) ) )
[1] "MSE:  361097965112.291"
MID_EUROPE[, predito:=predito]
MID_EUROPE <- MID_EUROPE %>% relocate(predito, .after = eur_value)
head(MID_EUROPE)

Dispersão

Avaliaçao de acerto X erro do modelo

MID_EUROPE %>%
  mutate(predito = predict(reg.test, .)) %>%
  plot_ly(x = ~eur_value,
          y= ~predito,
          type='scatter',
          mode='markers',
          text=~paste0("Real value: ", currency(eur_value, symbol='€', digits = 0L), 
                       "\nPredicted value: ", currency(predito, symbol='€', digits = 0L), 
                       "\nError: ", (eur_value - predito)),
          name="Dispersão") %>%
  add_segments(x=0, y=0, xend = 100000000, yend = 100000000, name="Equilíbrio")

Avaliação II

Avaliação dos preços (baseado nos dados que nunca viu antes!)

Predição dos preços

predito = predict(reg.test, fifa.18.cm)
print(paste("R2: ", R2_Score(predito, fifa.18.cm$eur_value) ) )
[1] "R2:  0.946997288889692"
print(paste("MSE: ", MSE(predito, fifa.18.cm$eur_value) ) )
[1] "MSE:  981991924923.896"
fifa.18.cm[, predito:=predito]
fifa.18.cm <- fifa.18.cm %>% relocate(predito, .after = eur_value)
head(fifa.18.cm)

Dispersão

fifa.18.cm %>%
  mutate(predito = predict(reg.test, .)) %>%
  plot_ly(x = ~eur_value,
          y= ~predito,
          type='scatter',
          mode='markers',
          text=~paste0("Real value: ", currency(eur_value, symbol='€', digits = 0L), 
                       "\nPredicted value: ", currency(predito, symbol='€', digits = 0L), 
                       "\nError: ", (eur_value - predito)),
          name="Dispersão") %>%
  add_segments(x=0, y=0, xend = 100000000, yend = 100000000, name="Equilíbrio")

Resultado

Resultado final com os meios campos e seus valores preditos

output <- MID_NOT_EUROPE %>%
  select(Position, name, eur_value) 

output[, eur_value := currency(fifa.18.cm$eur_value, symbol = '€', digits = 0L)]
output[, 'Preço "Calculado" (€)' := currency(fifa.18.cm$predito, symbol = '€', digits = 0L)]
output[, 'Potencial Valorização (€)' := currency((fifa.18.cm$predito - fifa.18.cm$eur_value), symbol='€', digits = 0L) ]
output[, 'Potencial Valorização (%)' := (percent((fifa.18.cm$predito - fifa.18.cm$eur_value) / 100000000)) ]

output <- output %>% 
  rename(
    'Posição' = Position,
    'Jogador' = name,
    'Preço de mercado' = eur_value
  )

head(output)
NA

Rodapé

Case de Advanced Analytics ***** Pedro Albuquerque - São Paulo - 2021

LS0tCnRpdGxlOiAiQ2FzZSBkZSBBZHZhbmNlZCBBbmFseXRpY3MiCmF1dGhvcjogIlBlZHJvIEFsYnVxdWVycXVlIgpzdWJ0aXRsZTogJ0FuYWxpc2UgcGFyYSBkZXRlcm1pbmFyIG9zIE1laW8gZGUgY2FtcG8gY29tIG1haW9yIHBvdGVuY2lhbCBjdXN0by1iZW5lZmljaW8gJwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgZmlnX3dpZHRoOiAxMAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQojIFByZWRpw6fDo28gZGUgcHJlw6dvcyBkZSBNZWlvIGRlIGNhbXBvIGVtIFIKIVtdKGltZy9NZWlvX2RlX2NhbXBvLmpwZyl7d2lkdGg9MTAwJX0gIAojIFNldHVwCkPDqWx1bGEgb2N1bHRhIHBhcmEgbyBzZXR1cDoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CnJtKGxpc3Q9bHMoKSkKbGlicmFyeShEQkkpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHRyZWUpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KE1MbWV0cmljcykKbGlicmFyeShEVCkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQojbGlicmFyeShSUG9zdGdyZXMpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoRGF0YUV4cGxvcmVyKQpsaWJyYXJ5KHJ3b3JsZG1hcCkKb3B0aW9ucyhzY2lwZW4gPSA5OTksIGRpZ2l0cyA9IDQpCmBgYAoKIyBPYnRlbsOnw6NvIGRlIERhZG9zCkNhcnJlZ2EgbyBub3NzbyBDU1YKTyBDU1YgZm9pIGdlcmFkbyBhdHJhdsOpcyBkZSBpbm5lcl9qb2lucyBkYXMgY29uc3VsdGFzIGRvIGJhbmNvIGRlIGRhZG9zIGNlZGlkbyBwZWxvIGRvIHByb2Zlc3NvciAuLi4gQXMgdGFiZWxhcyB1dGlsaXphZGFzIGZvcmFtOgonZnV0ZWJvbC5wbGF5ZXJzJywgJ2Z1dGVib2wuaGFiaWxpdGllcycsICdmdXRlYm9sLmZlYXR1cmVzJyBlICdmdXRlYm9sLmZpbmFuY2lhbCcgZSBvIHJlc3VsdGFkbyBmaW5hbCDDqSBsaWRvIGxvZ28gYWJhaXhvLgpPYnNlcnZhw6fDo286IMOJIG5lY2Vzc8OhcmlvIGFsdGVyYXIgbyBwYXRoIGRvIHJlYWRfY3N2IHBhcmEgYXBvbnRhciBjb3JyZXRhbWVudGUgbyBhcnF1aXZvIC5jc3YgZGUgYWNvcmRvIGNvbSBvIHNldSBTLiBPLgoKYGBge3J9CmRmIDwtIHJlYWRfY3N2KCIvbWVkaWEvbmphbmV0by9IRDEvRklBUC9QUk9HUkFNQU5ET19JQV9DT01fUi9maWZhMTgtZGF0YS1hbmFseXNpcy9tb2RlbC9kYXRhL2ZpZmExOC5jc3YiLCBsb2NhbGUgPSBsb2NhbGUoZW5jb2RpbmcgPSAiSVNPLTg4NTktMSIpKQpzZXREVChkZikKYGBgCiMgQW7DoWxpc2UgZG9zIGRhZG9zCsOJIG11aXRvIGltcG9ydGFudGUgcHJlcGFyYXJtb3Mgb3MgZGFkb3MgcGFyYSBhIGFuw6FsaXNlIGUgcHJlZGnDp8OjbyBxdWUgaXJlbW9zIGZhemVyLgoKIyMgVmlzdWFsaXphw6fDo28gaW5pY2lhbCBkb3MgZGFkb3MKQSBwcmltZWlyYSBjb2lzYSBxdWUgaXJlbW9zIGZhemVyIMOpIG9idGVyIG8gbsO6bWVybyBkZSBsaW5oYXMgZSBjb2x1bmFzIGRvIGRhdGFmcmFtZToKYGBge3J9CmRpbShkZikKYGBgCkRlcG9pcyB0cmF6ZW1vcyBvcyBwcmltZWlyb3MgcmVnaXN0cm9zIHV0aWxpemFuZG8gYSBmdW7Do28gaGVhZCgpCmBgYHtyfQpoZWFkKGRmKQpgYGAKRSBwb3IgZmltIGluY2x1w61tb3MgdG9kb3Mgb3MgY29udGluZW50ZXMgbm8gZGF0YWZyYW1lCmBgYHtyfQpBZnJpY2E8LWMoJ0FsZ2VyaWEnLCdBbmdvbGEnLCdCZW5pbicsJ0JvdHN3YW5hJywnQnVya2luYScsJ0J1cnVuZGknLCdDYW1lcm9vbicsJ0NhcGUgVmVyZGUnLCdDZW50cmFsIEFmcmljYW4gUmVwdWJsaWMnLCdDaGFkJywnQ29tb3JvcycsJ0NvbmdvJywnQ29uZ28gRGVtb2NyYXRpYyBSZXB1YmxpYyBvZicsJ0RqaWJvdXRpJywnRWd5cHQnLCdFcXVhdG9yaWFsIEd1aW5lYScsJ0VyaXRyZWEnLCdFdGhpb3BpYScsJ0dhYm9uJywnR2FtYmlhJywnR2hhbmEnLCdHdWluZWEnLCdHdWluZWEtQmlzc2F1JywnSXZvcnkgQ29hc3QnLCdLZW55YScsJ0xlc290aG8nLCdMaWJlcmlhJywnTGlieWEnLCdNYWRhZ2FzY2FyJywnTWFsYXdpJywnTWFsaScsJ01hdXJpdGFuaWEnLCdNYXVyaXRpdXMnLCdNb3JvY2NvJywnTW96YW1iaXF1ZScsJ05hbWliaWEnLCdOaWdlcicsJ05pZ2VyaWEnLCdSd2FuZGEnLCdTYW8gVG9tZSBhbmQgUHJpbmNpcGUnLCdTZW5lZ2FsJywnU2V5Y2hlbGxlcycsJ1NpZXJyYSBMZW9uZScsJ1NvbWFsaWEnLCdTb3V0aCBBZnJpY2EnLCdTb3V0aCBTdWRhbicsJ1N1ZGFuJywnU3dhemlsYW5kJywnVGFuemFuaWEnLCdUb2dvJywnVHVuaXNpYScsJ1VnYW5kYScsJ1phbWJpYScsJ1ppbWJhYndlJywnQnVya2luYSBGYXNvJykKQW50YXJjdGljYTwtYygnRmlqaScsJ0tpcmliYXRpJywnTWFyc2hhbGwgSXNsYW5kcycsJ01pY3JvbmVzaWEnLCdOYXVydScsJ05ldyBaZWFsYW5kJywnUGFsYXUnLCdQYXB1YSBOZXcgR3VpbmVhJywnU2Ftb2EnLCdTb2xvbW9uIElzbGFuZHMnLCdUb25nYScsJ1R1dmFsdScsJ1ZhbnVhdHUnKQpBc2lhPC1jKCdBZmdoYW5pc3RhbicsJ0JhaHJhaW4nLCdCYW5nbGFkZXNoJywnQmh1dGFuJywnQnJ1bmVpJywnQnVybWEgKE15YW5tYXIpJywnQ2FtYm9kaWEnLCdDaGluYScsJ0Vhc3QgVGltb3InLCdJbmRpYScsJ0luZG9uZXNpYScsJ0lyYW4nLCdJcmFxJywnSXNyYWVsJywnSmFwYW4nLCdKb3JkYW4nLCdLYXpha2hzdGFuJywnTm9ydGggS29yZWEnLCdTb3V0aCBLb3JlYScsJ0t1d2FpdCcsJ0t5cmd5enN0YW4nLCdMYW9zJywnTGViYW5vbicsJ01hbGF5c2lhJywnTWFsZGl2ZXMnLCdNb25nb2xpYScsJ05lcGFsJywnT21hbicsJ1Bha2lzdGFuJywnUGhpbGlwcGluZXMnLCdRYXRhcicsJ1J1c3NpYW4gRmVkZXJhdGlvbicsJ1NhdWRpIEFyYWJpYScsJ1NpbmdhcG9yZScsJ1NyaSBMYW5rYScsJ1N5cmlhJywnVGFqaWtpc3RhbicsJ1RoYWlsYW5kJywnVHVya2V5JywnVHVya21lbmlzdGFuJywnVW5pdGVkIEFyYWIgRW1pcmF0ZXMnLCdVemJla2lzdGFuJywnVmlldG5hbScsJ1llbWVuJywnUnVzc2lhJykKRXVyb3BlPC1jKCdBbGJhbmlhJywnQW5kb3JyYScsJ0FybWVuaWEnLCdBdXN0cmlhJywnQXplcmJhaWphbicsJ0JlbGFydXMnLCdCZWxnaXVtJywnQm9zbmlhIGFuZCBIZXJ6ZWdvdmluYScsJ0J1bGdhcmlhJywnQ3JvYXRpYScsJ0N5cHJ1cycsJ0N6ZWNoIFJlcHVibGljJywnRGVubWFyaycsJ0VzdG9uaWEnLCdGaW5sYW5kJywnRnJhbmNlJywnR2VvcmdpYScsJ0dlcm1hbnknLCdHcmVlY2UnLCdIdW5nYXJ5JywnSWNlbGFuZCcsJ0lyZWxhbmQnLCdJdGFseScsJ0xhdHZpYScsJ0xpZWNodGVuc3RlaW4nLCdMaXRodWFuaWEnLCdMdXhlbWJvdXJnJywnTWFjZWRvbmlhJywnTWFsdGEnLCdNb2xkb3ZhJywnTW9uYWNvJywnTW9udGVuZWdybycsJ05ldGhlcmxhbmRzJywnTm9yd2F5JywnUG9sYW5kJywnUG9ydHVnYWwnLCdSb21hbmlhJywnU2FuIE1hcmlubycsJ1Njb3RsYW5kJywnU2VyYmknLCdTbG92YWtpYScsJ1Nsb3ZlbmlhJywnU3BhaW4nLCdTd2VkZW4nLCdTd2l0emVybGFuZCcsJ1VrcmFpbmUnLCdFbmdsYW5kJywnVmF0aWNhbiBDaXR5JywnUmVwdWJsaWMgb2YgSXJlbGFuZCcsJ1dhbGVzJykKTm9ydGhfYW1lcmljYTwtYygnQW50aWd1YSBhbmQgQmFyYnVkYScsJ0JhaGFtYXMnLCdCYXJiYWRvcycsJ0JlbGl6ZScsJ0NhbmFkYScsJ0Nvc3RhIFJpY2EnLCdDdWJhJywnRG9taW5pY2EnLCdEb21pbmljYW4gUmVwdWJsaWMnLCdFbCBTYWx2YWRvcicsJ0dyZW5hZGEnLCdHdWF0ZW1hbGEnLCdIYWl0aScsJ0hvbmR1cmFzJywnSmFtYWljYScsJ01leGljbycsJ05pY2FyYWd1YScsJ1BhbmFtYScsJ1NhaW50IEtpdHRzIGFuZCBOZXZpcycsJ1NhaW50IEx1Y2lhJywnU2FpbnQgVmluY2VudCBhbmQgdGhlIEdyZW5hZGluZXMnLCdUcmluaWRhZCBhbmQgVG9iYWdvJywnVW5pdGVkIFN0YXRlcycpClNvdXRoX2FtZXJpY2E8LWMoJ0FyZ2VudGluYScsJ0JvbGl2aWEnLCdCcmF6aWwnLCdDaGlsZScsJ0NvbG9tYmlhJywnRWN1YWRvcicsJ0d1eWFuYScsJ1BhcmFndWF5JywnUGVydScsJ1N1cmluYW1lJywnVXJ1Z3VheScsJ1ZlbmV6dWVsYScpCgpkZlssIGNvbnRpbmVudDo9IGRmJG5hdGlvbmFsaXR5XQpkZiA8LSBkZiAlPiUgcmVsb2NhdGUoY29udGluZW50LCAuYWZ0ZXIgPSBuYXRpb25hbGl0eSkKCmRmJGNvbnRpbmVudFtkZiRjb250aW5lbnQgJWluJSBBZnJpY2EgXSA8LSAiQWZyaWNhIgpkZiRjb250aW5lbnRbZGYkY29udGluZW50ICVpbiUgQW50YXJjdGljYSBdIDwtICJBbnRhcmN0aWNhIgpkZiRjb250aW5lbnRbZGYkY29udGluZW50ICVpbiUgQXNpYSBdIDwtICJBc2lhIgpkZiRjb250aW5lbnRbZGYkY29udGluZW50ICVpbiUgRXVyb3BlIF0gPC0gIkV1cm9wZSIKZGYkY29udGluZW50W2RmJGNvbnRpbmVudCAlaW4lIE5vcnRoX2FtZXJpY2EgXSA8LSAiTm9ydGhfYW1lcmljYSIKZGYkY29udGluZW50W2RmJGNvbnRpbmVudCAlaW4lIFNvdXRoX2FtZXJpY2EgXSA8LSAiU291dGhfYW1lcmljYSIKCmBgYAojIyBHcmFmaWNvcyBkZSBhbmFsaXNlIGRvcyBkYWRvcwpBbnRlcyBkZSBpbmljaWFyIGEgbm9zc2EgYW7DoWxpc2UsIGlyZW1vcyBwbG90YXIgbyBkYXRhZnJhbWUgcGFyYSBhbmFsaXNhciBvIGdyw6FmaWNvIHJlc3VsdGFudGUsIHBhcmEgaWRlbnRpZmljYXIgYXMgbWVsaG9yZXMgdmFyacOhdmVpcy4gCmBgYHtyfQpwbG90X2ludHJvKGRmKQpgYGAKVXNhbW9zIGEgZnVuw6fDo28gcGxvdF9taXNzaW5nKCkgcGFyYSBtb3N0cmFyIHF1YWlzIGNvbHVuYXMgcG9zc3VlbSBkYWRvcyBmYWx0YW50ZXMgZW0gbm9zc28gZGF0YWZyYW1lLgoKYGBge3J9CnBsb3RfbWlzc2luZyhkZikKYGBgCgpFbnTDo28gY3JpYW1vcyB1bSBtYXBhIG1vc3RyYW5kbyBhIGRpc3RyaWJ1acOnw6NvIGRvcyBqb2dhZG9yZXMgcG9yIHBhw61zLgpgYGB7cn0KcGFpcyA8LSBkZlssLk4sIGJ5PW5hdGlvbmFsaXR5XQpmciA8LSBqb2luQ291bnRyeURhdGEyTWFwKGRGPXBhaXMsIGpvaW5Db2RlID0gIk5BTUUiLCBuYW1lSm9pbkNvbHVtbiA9ICJuYXRpb25hbGl0eSIsIHZlcmJvc2UgPSBGKQoKbWFwQ291bnRyeURhdGEobWFwVG9QbG90ID0gZnIsbmFtZUNvbHVtblRvUGxvdCA9ICJOIixjYXRNZXRob2QgPSAiZml4ZWRXaWR0aCIsCiAgICAgICAgICAgICAgIG9jZWFuQ29sID0gInN0ZWVsYmx1ZTEiLG1pc3NpbmdDb3VudHJ5Q29sID0gIndoaXRlIiwKICAgICAgICAgICAgICAgbWFwVGl0bGUgPSAiSm9nYWRvcmVzIHBvciBwYcOtcyIsCiAgICAgICAgICAgICAgIGFzcGVjdCA9ICJ2YXJpYWJsZSIpIAoKYGBgCgpIaXN0b2dyYW1hIGRlIGpvZ2Fkb3JlcyBwb3Igb3ZlcmFsbApDb25zZWd1aW1vcyBpZGVudGlmaWNhciB1bWEgZ3JhbmRlIGNvbmNlbnRyYcOnw6NvIGRlIGpvZ2Fkb3JlcyBjb20gb3ZlcmFsbCBlbnRyZSA2MCBlIDczCmBgYHtyfQpoaXN0b2dyYW0gPC0gcGxvdF9seSh4ID0gfmRmJG92ZXJhbGwsCiAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiaGlzdG9ncmFtIiwKICAgICAgICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJsaWdodGdyYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImRhcmtncmF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDEpKSkgJT4lCiAgbGF5b3V0KHRpdGxlID0gIkhpc3RvZ3JhbWEgcG9yIG92ZXJhbGwgZG9zIGpvZ2Fkb3JlcyAoQ2VudGVyLW1pZCkiLAogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiT3ZlcmFsbCIsCiAgICAgICAgICAgICAgICAgICAgICB6ZXJvbGluZSA9IEZBTFNFKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlF1YW50aWRhZGUiLAogICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmUgPSBGQUxTRSkpCgpoaXN0b2dyYW0KYGBgCgoKIyMgRmlsdHJhbmRvIE1laW8gY2FtcG8KQWluZGEgbmEgYW7DoWxpc2UsIGZpbHRyYW1vcyBvcyBtZWlvcyBjYW1wbyBkb3MgdGltZXMKYGBge3J9Ck1JRF9FVVJPUEUgPC0gZGYgJT4lCiAgZmlsdGVyKFBvc2l0aW9uID09ICJDZW50ZXItbWlkIiAmIGNvbnRpbmVudCA9PSAiRXVyb3BlIikKCmhlYWQoTUlEX0VVUk9QRSkKYGBgCkVudMOjbywgc2VsZWNpb25hbW9zIG9zIG1laW9zIGNhbXBvcyBkYSB2YXJpw6F2ZWwKYGBge3J9CiMtLSBiYXNlIHBhcmEgdmFsaWRhY2FvCk1JRF9OT1RfRVVST1BFIDwtIGRmICU+JQogIGZpbHRlcihQb3NpdGlvbiA9PSAiQ2VudGVyLW1pZCIgJiBjb250aW5lbnQgIT0gIkV1cm9wZSIpCgpoZWFkKE1JRF9OT1RfRVVST1BFKQpgYGAKCiMjIFJlbW92ZW5kbyBkYWRvcyAKUHJvIMO6bHRpbW8sIGxpbXBhbW9zIG8gZGF0YWZyYW1lIHJlbW92ZW5kbyBtaXNzaW5nIGRhdGEgZSB2YXJpw6F2ZWlzIG7Do28gbsO6bWVyaWNhcyBkYSBiYXNlIGRlIHRyZWluby4KYGBge3J9Ck1JRF9FVVJPUEUgPC0gTUlEX0VVUk9QRSAlPiUgCiAgc2VsZWN0X2lmKH4gIWFueShpcy5uYSguKSkpICU+JQogIHNlbGVjdF9pZih+IGFueShpcy5udW1lcmljKC4pKSkKCmJveHBsb3QoTUlEX0VVUk9QRSkKYGBgCkUgcG9yIGZpbSwgcmVtb3ZlbW9zIG9zIG1pc3NpbmcgZGF0YXMgZSB2YXJpw6F2ZWlzIG7Do28gbnVtw6lyaWNhcyBkYSBub3NzYSBiYXNlIGRlIHZhbGlkYcOnw6NvLgpgYGB7cn0KZmlmYS4xOC5jbSA8LSBNSURfTk9UX0VVUk9QRSAlPiUgCiAgc2VsZWN0X2lmKH4gIWFueShpcy5uYSguKSkpICU+JQogIHNlbGVjdF9pZih+IGFueShpcy5udW1lcmljKC4pKSkKCmhlYWQoZmlmYS4xOC5jbSkKYGBgCgojICBUcmVpbmFtZW50bwpOZXNzYSBldGFwYSBpbmljaWFtb3MgbyBwcm9jZXNzbyBkZSB0cmVpbmFtZW50byBkbyBub3NzbyBtb2RlbG8uCgojIyBCb3hwbG90IApHcmFmaWNvIGRlIGJveHBsb3Qgbm9zIG1vc3RyYSBxdWUgZXhpc3RlbSBqb2dhZG9yZXMgY29tIHZhbG9yZXMgZGlzY3JlcGFudGVzIGVtIHJlbGHDp8OjbyBhcyBkZW1haXMuIApgYGB7cn0KYm94cGxvdChNSURfRVVST1BFKQoKCmBgYAoKIyMgQ29ycmVsYcOnw6NvIAoKR3LDoWZpY28gcGFyYSBtb3N0cmFyIGEgY29ycmVsYcOnw6NvIGRlIHRvZGFzIGFzIHZhcmnDoXZlaXMuIApgYGB7cn0KY29yck1hdHJpeCA8LSBjb3IoTUlEX0VVUk9QRSkKY29ycnBsb3QubWl4ZWQoY29yck1hdHJpeCwgCiAgICAgICAgICAgICAgIGxvd2VyID0gImVsbGlwc2UiLCAKICAgICAgICAgICAgICAgdXBwZXIgPSAibnVtYmVyIiwKICAgICAgICAgICAgICAgdGwucG9zID0gImx0IiwKICAgICAgICAgICAgICAgdGwuY29sID0gImJsYWNrIiwKICAgICAgICAgICAgICAgb3JkZXI9ImhjbHVzdCIsCiAgICAgICAgICAgICAgIGhjbHVzdC5tZXRob2QgPSAid2FyZC5EIiwKICAgICAgICAgICAgICAgYWRkcmVjdCA9IDMpCgpgYGAKIyMgRGVmaW5pw6fDo28gZG8gbW9kZWxvIGRlIE1MIAoKVGVzdGFtb3MgYXMgZnVuw6fDtWVzIG1sKCkgZSByYW5kb21GbG9yZXN0KCkgZSBhIGZ1bsOnw6NvIHJhbmRvbUZsb3Jlc3QoKSBzZSBtb3N0cm91IG1haXMgYXNzZXJ0aXZvIHBhcmEgbyBub3NzbyBtb2RlbG8uCmBgYHtyfQpzZXQuc2VlZCgxKQpyZWcudGVzdCA8LSByYW5kb21Gb3Jlc3QoZm9ybXVsYSA9IGV1cl92YWx1ZSB+IC4sIAogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IE1JRF9FVVJPUEUsIAogICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWU9MTAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHByb3hpbWl0eT1UUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGxvY2FsSW1wPVRSVUUpCnBsb3QocmVnLnRlc3QpCgpgYGAKIyBQcmVkacOnw6NvCiMjIEF2YWxpYcOnw6NvICAKUHJlZGnDp8OjbyBkb3MgcHJlw6dvcyAKYGBge3J9CnByZWRpdG8gPSBwcmVkaWN0KHJlZy50ZXN0LCBNSURfRVVST1BFKQpwcmludChwYXN0ZSgiUjI6ICIsIFIyX1Njb3JlKHByZWRpdG8sIE1JRF9FVVJPUEUkZXVyX3ZhbHVlKSApICkKcHJpbnQocGFzdGUoIk1TRTogIiwgTVNFKHByZWRpdG8sIE1JRF9FVVJPUEUkZXVyX3ZhbHVlKSApICkKYGBgCmBgYHtyfQpNSURfRVVST1BFWywgcHJlZGl0bzo9cHJlZGl0b10KTUlEX0VVUk9QRSA8LSBNSURfRVVST1BFICU+JSByZWxvY2F0ZShwcmVkaXRvLCAuYWZ0ZXIgPSBldXJfdmFsdWUpCmhlYWQoTUlEX0VVUk9QRSkKYGBgCiMjIERpc3BlcnPDo28KQXZhbGlhw6dhbyBkZSBhY2VydG8gWCBlcnJvIGRvIG1vZGVsbyAKCmBgYHtyfQpNSURfRVVST1BFICU+JQogIG11dGF0ZShwcmVkaXRvID0gcHJlZGljdChyZWcudGVzdCwgLikpICU+JQogIHBsb3RfbHkoeCA9IH5ldXJfdmFsdWUsCiAgICAgICAgICB5PSB+cHJlZGl0bywKICAgICAgICAgIHR5cGU9J3NjYXR0ZXInLAogICAgICAgICAgbW9kZT0nbWFya2VycycsCiAgICAgICAgICB0ZXh0PX5wYXN0ZTAoIlJlYWwgdmFsdWU6ICIsIGN1cnJlbmN5KGV1cl92YWx1ZSwgc3ltYm9sPSfigqwnLCBkaWdpdHMgPSAwTCksIAogICAgICAgICAgICAgICAgICAgICAgICJcblByZWRpY3RlZCB2YWx1ZTogIiwgY3VycmVuY3kocHJlZGl0bywgc3ltYm9sPSfigqwnLCBkaWdpdHMgPSAwTCksIAogICAgICAgICAgICAgICAgICAgICAgICJcbkVycm9yOiAiLCAoZXVyX3ZhbHVlIC0gcHJlZGl0bykpLAogICAgICAgICAgbmFtZT0iRGlzcGVyc8OjbyIpICU+JQogIGFkZF9zZWdtZW50cyh4PTAsIHk9MCwgeGVuZCA9IDEwMDAwMDAwMCwgeWVuZCA9IDEwMDAwMDAwMCwgbmFtZT0iRXF1aWzDrWJyaW8iKQpgYGAKCiMjIEF2YWxpYcOnw6NvIElJCiMjIyBBdmFsaWHDp8OjbyBkb3MgcHJlw6dvcyAoYmFzZWFkbyBub3MgZGFkb3MgcXVlIG51bmNhIHZpdSBhbnRlcyEpClByZWRpw6fDo28gZG9zIHByZcOnb3MgCmBgYHtyfQpwcmVkaXRvID0gcHJlZGljdChyZWcudGVzdCwgZmlmYS4xOC5jbSkKcHJpbnQocGFzdGUoIlIyOiAiLCBSMl9TY29yZShwcmVkaXRvLCBmaWZhLjE4LmNtJGV1cl92YWx1ZSkgKSApCnByaW50KHBhc3RlKCJNU0U6ICIsIE1TRShwcmVkaXRvLCBmaWZhLjE4LmNtJGV1cl92YWx1ZSkgKSApCmBgYApgYGB7cn0KZmlmYS4xOC5jbVssIHByZWRpdG86PXByZWRpdG9dCmZpZmEuMTguY20gPC0gZmlmYS4xOC5jbSAlPiUgcmVsb2NhdGUocHJlZGl0bywgLmFmdGVyID0gZXVyX3ZhbHVlKQpoZWFkKGZpZmEuMTguY20pCmBgYAojIyBEaXNwZXJzw6NvCmBgYHtyfQpmaWZhLjE4LmNtICU+JQogIG11dGF0ZShwcmVkaXRvID0gcHJlZGljdChyZWcudGVzdCwgLikpICU+JQogIHBsb3RfbHkoeCA9IH5ldXJfdmFsdWUsCiAgICAgICAgICB5PSB+cHJlZGl0bywKICAgICAgICAgIHR5cGU9J3NjYXR0ZXInLAogICAgICAgICAgbW9kZT0nbWFya2VycycsCiAgICAgICAgICB0ZXh0PX5wYXN0ZTAoIlJlYWwgdmFsdWU6ICIsIGN1cnJlbmN5KGV1cl92YWx1ZSwgc3ltYm9sPSfigqwnLCBkaWdpdHMgPSAwTCksIAogICAgICAgICAgICAgICAgICAgICAgICJcblByZWRpY3RlZCB2YWx1ZTogIiwgY3VycmVuY3kocHJlZGl0bywgc3ltYm9sPSfigqwnLCBkaWdpdHMgPSAwTCksIAogICAgICAgICAgICAgICAgICAgICAgICJcbkVycm9yOiAiLCAoZXVyX3ZhbHVlIC0gcHJlZGl0bykpLAogICAgICAgICAgbmFtZT0iRGlzcGVyc8OjbyIpICU+JQogIGFkZF9zZWdtZW50cyh4PTAsIHk9MCwgeGVuZCA9IDEwMDAwMDAwMCwgeWVuZCA9IDEwMDAwMDAwMCwgbmFtZT0iRXF1aWzDrWJyaW8iKQpgYGAKCiMgIFJlc3VsdGFkbwoKUmVzdWx0YWRvIGZpbmFsIGNvbSBvcyBtZWlvcyBjYW1wb3MgZSBzZXVzIHZhbG9yZXMgcHJlZGl0b3MKCmBgYHtyfQpvdXRwdXQgPC0gTUlEX05PVF9FVVJPUEUgJT4lCiAgc2VsZWN0KFBvc2l0aW9uLCBuYW1lLCBldXJfdmFsdWUpIAoKb3V0cHV0WywgZXVyX3ZhbHVlIDo9IGN1cnJlbmN5KGZpZmEuMTguY20kZXVyX3ZhbHVlLCBzeW1ib2wgPSAn4oKsJywgZGlnaXRzID0gMEwpXQpvdXRwdXRbLCAnUHJlw6dvICJDYWxjdWxhZG8iICjigqwpJyA6PSBjdXJyZW5jeShmaWZhLjE4LmNtJHByZWRpdG8sIHN5bWJvbCA9ICfigqwnLCBkaWdpdHMgPSAwTCldCm91dHB1dFssICdQb3RlbmNpYWwgVmFsb3JpemHDp8OjbyAo4oKsKScgOj0gY3VycmVuY3koKGZpZmEuMTguY20kcHJlZGl0byAtIGZpZmEuMTguY20kZXVyX3ZhbHVlKSwgc3ltYm9sPSfigqwnLCBkaWdpdHMgPSAwTCkgXQpvdXRwdXRbLCAnUG90ZW5jaWFsIFZhbG9yaXphw6fDo28gKCUpJyA6PSAocGVyY2VudCgoZmlmYS4xOC5jbSRwcmVkaXRvIC0gZmlmYS4xOC5jbSRldXJfdmFsdWUpIC8gMTAwMDAwMDAwKSkgXQoKb3V0cHV0IDwtIG91dHB1dCAlPiUgCiAgcmVuYW1lKAogICAgJ1Bvc2nDp8OjbycgPSBQb3NpdGlvbiwKICAgICdKb2dhZG9yJyA9IG5hbWUsCiAgICAnUHJlw6dvIGRlIG1lcmNhZG8nID0gZXVyX3ZhbHVlCiAgKQoKaGVhZChvdXRwdXQpCgpgYGAKCioqKioqCiMgUm9kYXDDqQpDYXNlIGRlIEFkdmFuY2VkIEFuYWx5dGljcwoqKioqKgoqUGVkcm8gQWxidXF1ZXJxdWUgLSBTw6NvIFBhdWxvIC0gMjAyMSo=